fix(interactive): the trigger prompt was never actually submitted - #278
Merged
Conversation
Two defects, both in the injection step, both found by watching a real claude TUI in its tmux pane. The prompt was injected as a multiline payload. `tmux send-keys -l` sends the text literally, newlines included. That does not submit each line — it leaves the TUI in multi-line input, and the `Enter` that follows adds another line instead of sending. The default trigger prompt is `"<title>\n\nSee .task.md for full spec."`, so this was not an edge case: interactive triggers had never submitted their own prompt. The pane sat with the text visible and unsent until the 2-hour timeout, which reads as a hung agent rather than a delivery bug. `flatten_for_injection` collapses the prompt to one line. Nothing is lost — the detail lives in `.task.md` and `.agent.md`, and the prompt is only a pointer to them. The existing doc comment on `append_sentinel_to_prompt` already said the payload must stay newline-free, but the rule was only ever applied to the sentinel suffix, never to the prompt it was appended to. Its explanation of what a newline does was also wrong (it does not submit early); corrected to match what the pane actually shows. Enter was sent too soon after the text. Even flattened, the prompt stayed unsent: these TUIs ingest pasted text asynchronously and an immediate `Enter` is dropped. Confirmed by hand — the same pane submitted instantly when Enter was sent a moment later. A 600ms settle now separates the two. Generous on purpose: the cost of waiting is a fraction of a second on a run measured in minutes, and the cost of being early is a trigger that hangs for two hours. Verified end to end on a real column: prompt submitted, agent read `.agent.md` and followed it, fixed the planted bug, wrote its proof file, and emitted `<<<KAITENCODE_DONE:…>>>`, which the watcher detected and persisted to `agent_done_signaled_at`. The task deliberately did not advance — interactive completion is advisory, since the session stays alive for the human. `.task.md` and `.agent.md` were correctly absent from `git status`, confirming the exclude fix holds on this path too.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two defects in the injection step, both found by watching a real claude TUI in its tmux pane. Together they meant interactive trigger mode had never submitted its own prompt — the pane sat with the text visible and unsent until the 2-hour timeout, which reads as a hung agent rather than a delivery bug.
1. The prompt was injected as a multiline payload
tmux send-keys -lsends text literally, newlines included. That does not submit each line — it leaves the TUI in multi-line input, and theEnterthat follows adds another line instead of sending. Observed directly:…sitting in the composer indefinitely.
The default trigger prompt is
"<title>\n\nSee .task.md for full spec.", so this was never an edge case.flatten_for_injectioncollapses the prompt to one line. Nothing is lost — the detail lives in.task.mdand.agent.md; the prompt is only a pointer.Worth noting: the doc comment on
append_sentinel_to_promptalready said the payload must stay newline-free, but the rule was only ever applied to the sentinel suffix, never to the prompt it was appended to. Its explanation of what a newline does was also wrong — it doesn't submit early — so that's corrected to match what the pane actually shows.2. Enter was sent too soon after the text
Even flattened, the prompt stayed unsent. These TUIs ingest pasted text asynchronously and an immediate
Enteris dropped. Confirmed by hand: the same pane submitted instantly when Enter was sent a moment later.A 600ms settle now separates the two — generous on purpose, since the cost of waiting is a fraction of a second on a run measured in minutes, and the cost of being early is a trigger that hangs for two hours.
Verified end to end on a real column
.agent.mdand followed itreturn a - b→return a + bBENTO-INTERACTIVE<<<KAITENCODE_DONE:…>>>agent_done_signaled_atsetThat last row is correct behaviour, not a defect: interactive completion is advisory because the session stays alive for the human. The code says so itself, and the run confirmed the advisory path works.
Also confirmed on this path:
git statusin the worktree showed onlymath.jsandagent-proof.txt—.task.mdand.agent.mdcorrectly excluded, so #276's fix holds here too.Checks
cargo clippy -D warnings·cargo test --lib566 (+2) ·tsc·eslint·test:ipc·test:type-scale·vitest444.